In VISUALIZATION VIBES project Study 2, participants completed an attitutde eliciation survey, asking questions about their attitude toward (5) stimulus images (data visualizations). Each participant was randomly assigned to one of 6 stimulus blocks, each containing 1 image from each of (4) ‘embellishment categories’ (ranging from most abstract to most figural). Each participant started by responding to questions for a single ‘common’ stimulus (B0-0). Two participant recruitment pools were used: Prolific, with a smaller set of participants recruited from Tumblr (to replicate and compare survey results to Study 1 interviews with participants sourced from Tumblr).
This notebook contains code to replicate quantitative analysis of data from Study 2 reported in CHI submission #5584.
# Custom ggplot theme to make pretty plots
# Get the font at https://fonts.google.com/specimen/Barlow+Semi+Condensed
theme_clean <- function() {
theme_minimal(base_family = "Barlow Semi Condensed") +
theme(panel.grid.minor = element_blank(),
plot.title = element_text(family = "BarlowSemiCondensed-Bold"),
axis.title = element_text(family = "BarlowSemiCondensed-Medium"),
strip.text = element_text(family = "BarlowSemiCondensed-Bold",
size = rel(1), hjust = 0),
strip.background = element_rect(fill = "grey80", color = NA))
}
set_theme(base = theme_clean())
############## SETUP Colour Palettes
#https://www.r-bloggers.com/2022/06/custom-colour-palettes-for-ggplot2/
## list of color pallettes
my_colors = list(
politics = c("#184aff","#5238bf", "#4f4a52" ,"#84649c", "#ff0000"),
blackred = c("black","red"),
greys = c("#707070","#999999","#C2C2C2"),
greens = c("#ADC69D","#81A06D","#567E39","#2D5D16","#193E0A"),
smallgreens = c("#ADC69D","#567E39","#193E0A"), ## MALE FEMALE OTHER
olives = c("#CDCEA1","#B8B979","#A0A054","#78783F","#50502A","#35351C"),
lightblues = c("#96C5D2","#61A2B2","#3C8093","#2C6378","#1F4A64"),
darkblues = c("#7AAFE1","#3787D2","#2A73B7","#225E96","#1A4974","#133453"),
reds = c("#D9B8BD","#CE98A2","#B17380","#954E5F","#78263E","#62151F"),
traffic = c("#CE98A2","#81A06D","yellow"),
questions = c("#B17380","#3787D2", "#567E39", "#EE897F"),
tools= c("#D55662","#EE897F","#F5D0AD","#A0B79B","#499678","#2D363A"), #? ... design.....vis...... programming
encounter = c("#8E8E8E","#729B7D"), ##SCROLL ENGAGE
actions2 = c("#8E8E8E","#729B7D"),
actions4 = c("#8E8E8E", "#A3A3A3","#729B7D","#499678"),
actions3 = c("#8E8E8E","#99b898ff","#fdcea8ff"),
actions = c("#8E8E8E","#2A363B","#99b898ff","#fdcea8ff","#ff837bff","#e84a60ff"),
platforms = c("#5D93EA","#FF70CD", "#3BD3F5", "#8B69B5","black"),
amy_gradient = c("#ac57aa", "#9e5fa4", "#90689f", "#827099", "#747894", "#66818e", "#578988", "#499183", "#3b997d", "#2da278", "#1faa72"),
my_favourite_colours = c("#702963", "#637029", "#296370")
)
## function for using palettes
my_palettes = function(name, n, all_palettes = my_colors, type = c("discrete","continuous"), direction = c("1","-1")) {
palette = all_palettes[[name]]
if (missing(n)) {
n = length(palette)
}
type = match.arg(type)
out = switch(type,
continuous = grDevices::colorRampPalette(palette)(n),
discrete = palette[1:n]
)
out = switch(direction,
"1" = out,
"-1" = palette[n:1])
structure(out, name = name, class = "palette")
}
# ############## RETURNS SD STACKED AND COLORED BY BY X
# ## LOOP STYLE
# multi_sd <- function (data, left, right, x, y, color) {
#
# # g <- ggplot(df, aes(y = .data[[x]], x = {{y}}, color = {{color}}))+
# g <- ggplot(data, aes(y = .data[[x]], x = .data[[y]], color = .data[[color]]))+
# geom_boxplot(width = 0.5) +
# geom_jitter(width = 0.1, alpha=0.5) +
#
# scale_y_continuous(limits=c(-1,101)) +
# labs(x="", y="") +
# coord_flip() +
# guides(
# y = guide_axis_manual(labels = left),
# y.sec = guide_axis_manual(labels = right)
# ) + theme_minimal()
#
# return(g)
# }
#
#
# ############## RETURNS SINGLE SD
# ## LOOP STYLE
# single_sd <- function (data, left, right, x) {
#
# g <- ggplot(data, aes(y = {{x}}, x = ""))+
# geom_boxplot(width = 0.5) +
# geom_jitter(width = 0.1, alpha=0.5) +
# scale_y_continuous(limits=c(-1,101)) +
# labs(x="", y="") +
# coord_flip() +
# guides(
# y = guide_axis_manual(labels = left),
# y.sec = guide_axis_manual(labels = right)
# ) + theme_minimal()
#
# return(g)
# }
# ######## RETURNS SINGLE SD
# ## APPLY STYLE
plot_sd = function (data, column, type, mean, facet, facet_by, boxplot, labels) {
ggplot(data, aes(y = .data[[column]], x="")) +
{if(boxplot) geom_boxplot(width = 0.5) } +
geom_jitter(width = 0.1, alpha=0.2, {if(facet) aes(color=.data[[facet_by]])}) +
{if(mean)
stat_summary(fun="mean", geom="point", shape=20, size=5, color="blue", fill="blue")
} +
{if(mean)
## assumes data has been passed in with mean column at m
# stat_summary(fun="mean", geom="text", colour="blue", fontface = "bold",
# vjust=-1.25, hjust = 0.50, aes( label=round(..y.., digits=0)))
stat_summary(fun="mean", geom="text", colour="blue", fontface = "bold",
vjust=-1.25, hjust = 0.50, aes( label=round(..y.., digits=0)))
} +
{if(facet) facet_grid(.data[[facet_by]] ~ .)} +
# scale_y_continuous(limits=c(-1,101)) +
labs(x="", y="") +
coord_flip() +
{if(type == "S")
guides(
y = guide_axis_manual(labels = labels[column,"left"]),
y.sec = guide_axis_manual(labels = labels[column,"right"])
)} +
{if(type == "Q")
guides(
y = guide_axis_manual(labels = labels[q,"left"]),
y.sec = guide_axis_manual(labels = labels[q,"right"])
)} +
theme_minimal() +
labs (
caption = column
) + easy_remove_legend()
}
############## IMPORT REFERENCE FILES
ref_stimuli <- readRDS("data/input/REFERENCE/ref_stimuli.rds")
ref_surveys <- readRDS("data/input/REFERENCE/ref_surveys.rds")
ref_labels <- readRDS("data/input/REFERENCE/ref_labels.rds")
ref_labels_abs <- readRDS("data/input/REFERENCE/ref_labels_abs.rds")
############## SETUP Graph Labels
ref_stim_id <- levels(ref_stimuli$ID)
ref_cat_questions <- c("MAKER_ID","MAKER_AGE","MAKER_GENDER")
ref_free_response <- c("MAKER_DETAIL", "MAKER_EXPLAIN", "TOOL_DETAIL", "CHART_EXPLAIN")
ref_conf_questions <- c("MAKER_CONF", "AGE_CONF", "GENDER_CONF", "TOOL_CONF")
ref_sd_questions <- rownames(ref_labels)
ref_sd_questions_abs <- rownames(ref_labels_abs)
# ref_blocks <- c("block1", "block2", "block3", "block4", "block5", "block6")
ref_blocks <- c(1,2,3,4,5,6)
############## IMPORT DATA FILES
# df_data <- readRDS("data/output/df_data.rds") #1 row per participant — WIDE
df_participants <- readRDS("data/output/df_participants.rds") #1 row per participant — demographic
# df_questions <- readRDS("data/output/df_questions.rds") #1 row per question — LONG
# df_sd_questions_wide <- readRDS("data/output/df_sd_questions_wide.rds") # only sd questions WIDE
#
#
# df_tools <- readRDS("data/output/df_tools.rds") #multiselect format for tools Question
# df_actions <- readRDS("data/output/df_actions.rds") # multiselect format for action Question
# # # df_graphs_full <- readRDS("data/output/df_graphs_full.rds") #includes free response data
#
df_graphs <- readRDS("data/output/df_graphs.rds") #only categorical and numeric questions
df_sd_questions_long <- readRDS("data/output/df_sd_questions_long.rds") # only sd questions LONG
#
# ### DATA FILES WITH (VARIABLE-WISE) Z-SCORED SEMANTIC DIFFERENTIAL QS
# df_graphs_z <- readRDS("data/output/df_graphs_z.rds") #only categorical and numeric questions
# df_sd_questions_long_z <- readRDS("data/output/df_sd_questions_long_z.rds") # only sd questions LONG
#
#
# ### DATA FILES WITH ABSOLUTE VALUE SEMANTIC DIFFERENTIAL QS
# df_graphs_abs <- readRDS("data/output/df_graphs_abs.rds") #only categorical and numeric questions
# df_sd_questions_long_abs <- readRDS("data/output/df_sd_questions_long_abs.rds") # only sd questions LONG
df <- df_participants
## for descriptives paragraph
a.desc.duration <- psych::describe(df %>% pull(duration.min))
As Reported in Section 5.1.2 Procedure :
Across the entire sample, responses from (n = 318 ) participants ranged from 11 to 228 minutes, with a mean response time of 45 minutes, SD = 26.
rm(df, a.desc.duration)
df <- df_participants
## FOR DESCRIPTIVES PARAGRAPH
# #PROLIFIC
df.p <- df %>% filter(Distribution == "PROLIFIC")
desc.gender.p <- table(df.p$D_gender) %>% prop.table()
names(desc.gender.p) <- levels(df.p$D_gender)
p_participants <- nrow(df.p)
# #TUMBLR
df.t <- df %>% filter(Distribution == "TUMBLR")
desc.gender.t <- table(df.t$D_gender) %>% prop.table()
names(desc.gender.t) <- levels(df.t$D_gender)
t_participants <- nrow(df.t)
As Reported in Section 5.1.4 Participants :
For Study 2, a total of 318 participants were recruited from US-located English speaking users of TUMBLR (n = 78) and PROLIFIC (n = 240).
78 individuals from Tumblr participated in Study 2, ( 36% Female, 5% Male, 40% Non-binary, 17% Prefer to Self Describe, 3% Prefer Not to Say. Other).
240 individuals from PROLIFIC participated in Study 2, ( 54% Female, 42% Male, 3% Non-binary, 0 % Prefer Not to Say, 0% Prefer to Self Describe).
rm(df, df.p, desc.gender.p, p_participants, df.t, desc.gender.t, t_participants)
As Reported in Section 5.1.4 Participants, there were ~ 53 participants per stimulus block
df <- df_participants
table(df$Assigned.Block)
##
## 1 2 3 4 5 6
## 55 52 52 54 53 52
# cols = c("Block", "n")
# %>% kable(col.names = cols)
As Reported in Figure 5, descriptive statistics for in-scope survey questions.
# # library(tinytable)
# # library(webshot2)
#### CUSTOM HORIZONTAL STACKED BARPLOT
g <- function(d, ...){
p <- d$pal %>% unique
ggplot(d, aes(x="", fill=value)) +
geom_bar(stat="count", position = "stack") +
scale_fill_manual(values=my_colors[[p]]) +
coord_flip() + theme_void() + easy_remove_axes() + easy_remove_legend()
}
## SETUP LIST OF NUMERIC DATAFRAMES
all_q <- c("MAKER_CONF", "AGE_CONF", "GENDER_CONF", ref_sd_questions)
# ## SETUP NUMERIC DATAFRAME
df_num <- df_graphs %>% select(all_of(all_q))
## CALC MEANS
### MEANS
m <- sapply(df_num, FUN=mean)
m <- round(m,1)
m <- paste0("M=",m)
sd <- sapply(df_num, FUN=sd)
sd <- round(sd,1)
sd <- paste0("SD=",sd)
stat <- paste0(m," ",sd)
### CREATE LIST OF CATEGORICAL DATAFRAMES
id = df_graphs %>% select(MAKER_ID) %>%
pivot_longer(cols=1)%>% mutate(pal="reds") %>% as.data.frame()
age = df_graphs %>% select(MAKER_AGE) %>%
pivot_longer(cols=1)%>% mutate(pal="lightblues") %>% as.data.frame()
gender = df_graphs %>% select(MAKER_GENDER) %>%
pivot_longer(cols=1)%>% mutate(pal="smallgreens") %>% as.data.frame()
df_cat <- list()
df_cat[["MAKER_ID"]] <- id
df_cat[["MAKER_AGE"]] <- age
df_cat[["MAKER_GENDER"]] <- gender
## CALC CAT PROPORTIONS
n <- nrow(id)
m_id <- table(id) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100) %>% map_df(rev) #reverse reading order
stat_id <- paste0(m_id$value, "(", m_id$prop,"%)") %>% unlist() %>% paste0(collapse=''," ")
n <- nrow(age)
m_age <- table(age) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100)%>% map_df(rev)
stat_age <- paste0(m_age$value, "(", m_age$prop,"%)") %>% unlist() %>% paste0(collapse=''," ")
n <- nrow(gender)
m_gender <- table(gender) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100)%>% map_df(rev)
stat_gender <- paste0(m_gender$value, "(", m_gender$prop,"%)")%>% unlist() %>% paste0(collapse=''," ")
## SETUP QUESTIONS
questions <- c(ref_cat_questions, "MAKER_CONF", "AGE_CONF", "GENDER_CONF", ref_sd_questions)
#### SETUP TABLE
tab <- data.frame(
VARIABLE = questions,
DISTRIBUTION = "",
STATISTICS = c(stat_id, stat_age, stat_gender, stat)
)
### RENDER TABLE
t <- tinytable::tt(tab, theme = "void") %>%
plot_tt(j=2, i= 1:3, fun=g, data = df_cat, height = 1.5) %>%
plot_tt(j=2, i= 4:17, fun="density", data = df_num, color="darkgrey") %>%
style_tt(j=2, align="c")
t
| VARIABLE | DISTRIBUTION | STATISTICS |
|---|---|---|
| MAKER_ID | political(16%) news(19%) business(20%) education(25%) organization(7%) individual(12%) | |
| MAKER_AGE | gen-z(9%) millennial(40%) gen-x(41%) boomer(10%) | |
| MAKER_GENDER | Male(59%) Female(34%) Other(7%) | |
| MAKER_CONF | M=61.6 SD=23.4 | |
| AGE_CONF | M=60 SD=21.3 | |
| GENDER_CONF | M=54.2 SD=25.4 | |
| MAKER_DESIGN | M=48.1 SD=28.3 | |
| MAKER_DATA | M=42.7 SD=27.7 | |
| MAKER_POLITIC | M=47 SD=18.7 | |
| MAKER_ARGUE | M=54.9 SD=19.9 | |
| MAKER_SELF | M=44 SD=19.6 | |
| MAKER_ALIGN | M=52.7 SD=18.1 | |
| MAKER_TRUST | M=58 SD=18.6 | |
| CHART_TRUST | M=54.6 SD=23.2 | |
| CHART_INTENT | M=41.3 SD=31.5 | |
| CHART_LIKE | M=48.6 SD=26.4 | |
| CHART_BEAUTY | M=49.5 SD=28.9 |
if(GRAPH_SAVE){
save_tt(t, output="figs/CHI/fig_5_descriptives.png", overwrite = TRUE)
}
In addition to the descriptive analysis of stimuli in Block 2 that is reported in the manuscript, here we create visualize the semantic differential scale for each stimulus in Study 2.
#DEFINE STIMULI
df <- df_graphs
stimuli <- levels(df$STIMULUS)
graphs <- list()
## LOOP THROUGH EACH STIMULUS IN LIST
i = 0
for (s in stimuli){
i = i+1
# setup titles
title <- ref_stimuli %>% filter(ID == s) %>% select(NAME) ##TODO IF NOT WORK ref_stim_id
title <- paste(s,"|",title)
# setup dataframe
df <- df_sd_questions_long %>% select(1:8, STIMULUS, QUESTION, STIMULUS_CATEGORY, value) %>% filter(STIMULUS == s)
d <- left_join( x = df, y = ref_labels,
by = c("QUESTION" = "ref_sd_questions")) %>%
mutate(
category=factor(category, levels=c("COMPETENCY","MAKER","CHART")),
QUESTION = factor(QUESTION, levels=ref_sd_questions)) %>%
group_by(QUESTION) %>%
mutate(m=median(value)) ## calc median for printing on graph
# GGDIST HALFEYE (raincloud doesn't work b/c long tails)
(g <- d %>%
ggplot(aes(y = fct_rev(QUESTION), x = value, fill=category)) +
stat_halfeye(scale=0.8, density="bounded", point_interval = "median_qi", normalize="xy") +
## MEDIAN
stat_summary(fun=median, geom="text", fontface = "bold", size= 2.2,
vjust=+2, hjust = 0.50, aes(label=round(m, digits=0)))+
stat_summary(fun=median, geom="point", size=2) +
scale_color_manual(values = my_palettes(name="greys", direction = "1"))+
scale_fill_manual(values = my_palettes(name="greys", direction = "1"))+
guides(
y = guide_axis_manual(labels = rev(ref_labels$left), title = ""),
y.sec = guide_axis_manual(labels = rev(ref_labels$right))
) +
cowplot::draw_text(text = ref_sd_questions, x = 90, y= ref_sd_questions,size = 8, vjust=-2) +
labs (title = title, y = "", caption = "(point is median)") +
theme_minimal() + easy_remove_legend()
)
graphs[[i]] <- g
if(GRAPH_SAVE == TRUE){
ggsave(plot = g, path="figs/CHI/other_blocks/", filename =paste0(s,"_ggdist.png"), units = c("in"), width = 10, height = 14, bg='#ffffff' )}
} ## END LOOP
graphs
## [[1]]
##
## [[2]]
##
## [[3]]
##
## [[4]]
##
## [[5]]
##
## [[6]]
##
## [[7]]
##
## [[8]]
##
## [[9]]
##
## [[10]]
##
## [[11]]
##
## [[12]]
##
## [[13]]
##
## [[14]]
##
## [[15]]
##
## [[16]]
##
## [[17]]
##
## [[18]]
##
## [[19]]
##
## [[20]]
##
## [[21]]
##
## [[22]]
##
## [[23]]
##
## [[24]]
##
## [[25]]
sessionInfo()
## R version 4.3.2 (2023-10-31)
## Platform: x86_64-apple-darwin20 (64-bit)
## Running under: macOS Sonoma 14.6.1
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/New_York
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] rstatix_0.7.2 kSamples_1.2-10 SuppDists_1.1-9.7 latex2exp_0.9.6
## [5] equatiomatic_0.3.3 lmerTest_3.1-3 lme4_1.1-35.1 Matrix_1.6-5
## [9] sjPlot_2.8.15 see_0.8.2 report_0.5.8 parameters_0.21.5
## [13] performance_0.10.9 modelbased_0.8.7 insight_0.19.9 effectsize_0.8.6
## [17] datawizard_0.9.1 correlation_0.8.4 bayestestR_0.13.2 easystats_0.7.0
## [21] jtools_2.2.2 tidygraph_1.3.1 ggraph_2.2.1 interactions_1.2.0
## [25] ggsankey_0.0.99999 lessR_4.3.1 paletteer_1.6.0 plotly_4.10.4
## [29] RColorBrewer_1.1-3 viridis_0.6.5 viridisLite_0.4.2 ggdist_3.3.2
## [33] patchwork_1.2.0 ggh4x_0.2.8 ggeasy_0.1.4 corrplot_0.94
## [37] GGally_2.2.1 gghalves_0.1.4 ggstatsplot_0.12.2 ggformula_0.12.0
## [41] ggridges_0.5.6 scales_1.3.0 kableExtra_1.4.0 qacBase_1.0.3
## [45] webshot2_0.1.1 tinytable_0.4.0 summarytools_1.0.1 magrittr_2.0.3
## [49] lubridate_1.9.3 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4
## [53] purrr_1.0.2 readr_2.1.5 tidyr_1.3.1 tibble_3.2.1
## [57] ggplot2_3.5.0 tidyverse_2.0.0 psych_2.4.1 Hmisc_5.1-2
##
## loaded via a namespace (and not attached):
## [1] matrixStats_1.2.0 httr_1.4.7 numDeriv_2016.8-1.1
## [4] tools_4.3.2 backports_1.4.1 sjlabelled_1.2.0
## [7] utf8_1.2.4 R6_2.5.1 statsExpressions_1.5.3
## [10] lazyeval_0.2.2 withr_3.0.0 gridExtra_2.3
## [13] textshaping_0.3.7 cli_3.6.2 labeling_0.4.3
## [16] sass_0.4.9 BWStest_0.2.3 mvtnorm_1.2-4
## [19] robustbase_0.99-2 systemfonts_1.0.6 foreign_0.8-86
## [22] svglite_2.1.3 labelled_2.12.0 rstudioapi_0.15.0
## [25] generics_0.1.3 car_3.1-2 distributional_0.4.0
## [28] zip_2.3.1 leaps_3.1 interp_1.1-6
## [31] fansi_1.0.6 abind_1.4-5 lifecycle_1.0.4
## [34] yaml_2.3.8 carData_3.0-5 grid_4.3.2
## [37] promises_1.2.1 crayon_1.5.2 miniUI_0.1.1.1
## [40] lattice_0.22-5 cowplot_1.1.3 haven_2.5.4
## [43] chromote_0.3.1 magick_2.8.3 zeallot_0.1.0
## [46] pillar_1.9.0 knitr_1.45 tcltk_4.3.2
## [49] boot_1.3-30 estimability_1.5 codetools_0.2-19
## [52] glue_1.7.0 data.table_1.15.2 vctrs_0.6.5
## [55] png_0.1-8 gtable_0.3.4 rematch2_2.1.2
## [58] cachem_1.0.8 xfun_0.42 openxlsx_4.2.5.2
## [61] mime_0.12 coda_0.19-4.1 gmp_0.7-4
## [64] ellipsis_0.3.2 nlme_3.1-164 bslib_0.6.1
## [67] rpart_4.1.23 colorspace_2.1-0 nnet_7.3-19
## [70] mnormt_2.1.1 tidyselect_1.2.1 processx_3.8.4
## [73] emmeans_1.10.0 compiler_4.3.2 htmlTable_2.4.2
## [76] xml2_1.3.6 mosaicCore_0.9.4.0 checkmate_2.3.1
## [79] DEoptimR_1.1-3 multcompView_0.1-10 digest_0.6.35
## [82] minqa_1.2.6 rmarkdown_2.26 htmltools_0.5.7
## [85] pkgconfig_2.0.3 jpeg_0.1-10 base64enc_0.1-3
## [88] highr_0.10 fastmap_1.1.1 rlang_1.1.3
## [91] htmlwidgets_1.6.4 pryr_0.1.6 shiny_1.8.0
## [94] farver_2.1.1 jquerylib_0.1.4 jsonlite_1.8.8
## [97] rapportools_1.1 Formula_1.2-5 munsell_0.5.0
## [100] Rcpp_1.0.12 stringi_1.8.3 MASS_7.3-60.0.1
## [103] plyr_1.8.9 ggstats_0.5.1 parallel_4.3.2
## [106] ggrepel_0.9.5 sjmisc_2.8.9 deldir_2.0-4
## [109] graphlayouts_1.1.1 PMCMRplus_1.9.10 ggeffects_1.5.0
## [112] splines_4.3.2 pander_0.6.5 hms_1.1.3
## [115] sjstats_0.18.2 ps_1.7.6 igraph_2.0.3
## [118] reshape2_1.4.4 evaluate_0.23 latticeExtra_0.6-30
## [121] modelr_0.1.11 nloptr_2.0.3 tzdb_0.4.0
## [124] tweenr_2.0.3 httpuv_1.6.14 polyclip_1.10-7
## [127] ggforce_0.4.2 ggExtra_0.10.1 broom_1.0.5
## [130] xtable_1.8-4 Rmpfr_0.9-5 ggcorrplot_0.1.4.1
## [133] later_1.3.2 ragg_1.3.0 websocket_1.4.2
## [136] memoise_2.0.1 ellipse_0.5.0 cluster_2.1.6
## [139] timechange_0.3.0